﻿using System;
using Nofs.Net.nofs.metadata.interfaces;
using biz.ritter.javapi.lang.reflect;
using biz.ritter.javapi.lang;
using biz.ritter.javapi.util;
using Nofs.Net.Common.Interfaces.Library;


namespace Nofs.Net.nofs.metadata.XMLDriver
{

    public sealed class XMLAttributeAccessor : IAttributeAccessor
    {

        private readonly XMLFileSystem _fs;
        private readonly INoFSClassLoader _classLoader;

        public XMLAttributeAccessor(XMLFileSystem xmlFileSystem, INoFSClassLoader classLoader)
        {
            _fs = xmlFileSystem;
            _classLoader = classLoader;
        }

        private class DomainObjectFacade : IDomainObjectProperties
        {
            private XMLDomainObject _domainObject;

            public DomainObjectFacade(XMLDomainObject domainObject)
            {
                _domainObject = domainObject;
            }

            public bool CanWrite()
            {
                return _domainObject.CanWrite();
            }
        }

        public Class FindObjectTypeToCreateForMknod(Object obj, Method method) //throws Exception 
        {
            if (obj is Class || obj is Type)
            {
                throw new System.Exception("Cannot accept class or type");
            }
            return FindObjectTypeToCreateForMknod(obj, GetInnerCollectionType(method));
        }

        public Class FindObjectTypeToCreateForMkDir(Object obj, Method method) //throws Exception 
        {
            if (obj is Class || obj is Type)
            {
                throw new System.Exception("Cannot accept class or type");
            }
            return FindObjectTypeToCreateForMkDir(obj, GetInnerCollectionType(method));
        }

        public Class FindObjectTypeToCreateForMknod(Object obj) //throws Exception
        {
            if (obj is Class || obj is Type)
            {
                throw new System.Exception("Cannot accept class or type");
            }
            return FindObjectTypeToCreateForMknod(obj, GetInnerCollectionType(obj));
        }

        private Class FindObjectTypeToCreateForMknod(Object obj, Class innerCollectionType) //throws Exception
        {
            ArrayList<Class> matches = new ArrayList<Class>();
            if (!isFolderObject(innerCollectionType, true) && isDomainObject(innerCollectionType) && !Modifier.isAbstract(innerCollectionType.getModifiers()))
            {
                matches.add(innerCollectionType);
            }
            else if (Modifier.isAbstract(innerCollectionType.getModifiers()))
            {
                foreach (Class folderType in _classLoader.LoadClassesWithAnnotation(MarkerTypes.DomainObject))
                {
                    if (innerCollectionType.isAssignableFrom(folderType) && !isFolderObject(folderType) && !Modifier.isAbstract(folderType.getModifiers()))
                    {
                        matches.add(folderType);
                    }
                }
            }
            return FilterOutResults(obj, matches, innerCollectionType);
        }

        public Class FindObjectTypeToCreateForMkDir(Object obj) //throws Exception 
        {
            return FindObjectTypeToCreateForMkDir(obj, GetInnerCollectionType(obj));
        }

        private Class FindObjectTypeToCreateForMkDir(Object obj, Class innerCollectionType) //throws Exception 
        {
            ArrayList<Class> matches = new ArrayList<Class>();
            if (isFolderObject(innerCollectionType, true) && !Modifier.isAbstract(innerCollectionType.getModifiers()))
            {
                matches.add(innerCollectionType);
            }
            else if (Modifier.isAbstract(innerCollectionType.getModifiers()))
            {
                foreach (Class folderType in _classLoader.LoadClassesWithAnnotation(MarkerTypes.FolderObject))
                {
                    if (innerCollectionType.isAssignableFrom(folderType) && !Modifier.isAbstract(folderType.getModifiers()))
                    {
                        matches.add(folderType);
                    }
                }
            }
            return FilterOutResults(obj, matches, innerCollectionType);
        }

        private Class FilterOutResults(Object obj, ArrayList<Class> matches, Class innerCollectionType) //throws Exception 
        {
            if (matches.size() > 1)
            {
                IFolderObjectProperties folderProperties = GetFolderObject(obj);
                if (folderProperties.ChildTypeFilterMethod().length() > 0)
                {
                    ArrayList<Class> filteredTypes = new ArrayList<Class>();
                    foreach (Class type in matches)
                    {
                        if (InvokeFilterMethod(folderProperties, obj, type))
                        {
                            filteredTypes.add(type);
                        }
                    }
                    matches = filteredTypes;
                }
            }

            if (matches.size() == 1)
            {
                return matches.get(0);
            }
            else if (matches.size() > 1)
            {
                StringBuffer foundTypes = new StringBuffer();
                foundTypes.append("Found more than one possible type for mknod: ");
                foreach (Class found in matches)
                {
                    foundTypes.append(found.getSimpleName());
                    foundTypes.append(",");
                }
                throw new System.Exception(foundTypes.toString());
            }
            throw new System.Exception("Cannot find a type to use for creation. Inner Type = " + innerCollectionType.getSimpleName());
        }

        private bool InvokeFilterMethod(IFolderObjectProperties properties, Object obj, Class typeToCheck) //throws Exception 
        {
            foreach (Method method in obj.getClass().getMethods())
            {
                if (method.getName().compareTo(properties.ChildTypeFilterMethod()) == 0 &&
                    method.getReturnType() == typeof(bool).getClass() &&
                    method.getParameterTypes().Length == 1 &&
                    method.getParameterTypes()[0] == typeof(Class).getClass())
                {
                    return (bool)method.invoke(obj, typeToCheck);
                }
            }
            throw new System.Exception("could not find method with name = " + properties.ChildTypeFilterMethod() + " on object of type " + obj.getClass().getSimpleName());
        }


        public IDomainObjectProperties GetDomainObject(Object obj) //throws Exception 
        {
            if (!isDomainObject(obj))
            {
                throw new System.Exception("not a domain object");
            }
            XMLDomainObject domainObject = _fs.GetDomainObjectByName(GetNameFromClass(obj.getClass()));
            return new DomainObjectFacade(domainObject);
        }

        public IDomainObjectProperties GetDomainObject(Method method) //throws Exception 
        {
            if (!isDomainObject(method))
            {
                throw new System.Exception("not a domain object");
            }
            throw new System.Exception("not implemented");
        }

        public IFolderObjectProperties GetFolderObject(Object obj) //throws Exception 
        {
            if (!isFolderObject(obj))
            {
                throw new System.Exception("not a folder object");
            }
            XMLDomainObject domainObject = _fs.GetDomainObjectByName(GetNameFromClass(obj.getClass()));
            return new FolderObjectFacade(domainObject.FolderDescription());
        }

        private class FolderObjectFacade : IFolderObjectProperties
        {

            private XMLFolderDescription _folder;

            public FolderObjectFacade(XMLFolderDescription folder)
            {
                _folder = folder;
            }

            public bool CanAdd()
            {
                return _folder.CanAdd();
            }

            public String CanAddMethod()
            {
                return _folder.CanAddMethod() == null ? "" : _folder.CanAddMethod();
            }

            public bool CanRemove()
            {
                return _folder.CanRemove();
            }

            public String CanRemoveMethod()
            {
                return _folder.CanRemoveMethod() == null ? "" : _folder.CanRemoveMethod();
            }

            public String ChildTypeFilterMethod()
            {
                return _folder.ChildTypeFilterMethod();
            }
        }

        private class DummyFolderObjectProperties : IFolderObjectProperties
        {
            public bool CanAdd()
            {
                return true;
            }

            public String CanAddMethod()
            {
                return "";
            }

            public bool CanRemove()
            {
                return true;
            }

            public String CanRemoveMethod()
            {
                return "";
            }

            public String ChildTypeFilterMethod()
            {
                return "";
            }
        }

        public IFolderObjectProperties GetFolderObject(Method method) //throws Exception 
        {
            if (!isFolderObject(method))
            {
                throw new System.Exception("not a folder object");
            }
            return new DummyFolderObjectProperties();
        }

        public Class GetInnerCollectionType(Method method) //throws Exception
        {
            if (method.getGenericReturnType() == typeof(ParameterizedType).getClass())
            {
                return GetInnerType(method.getGenericReturnType().GetType());
            }
            throw new System.Exception("cannot find inner type");
        }

        private Class GetInnerType(ParameterizedType type) //throws Exception 
        {
            foreach (Type typeArgument in type.getActualTypeArguments())
            {
                if (typeArgument == typeof(Class))
                {
                    Class typeClassArgument = typeArgument.getClass();
                    if (_fs.TryGetDomainObjectByName(GetNameFromClass(typeClassArgument)) != null)
                    {
                        return typeClassArgument;
                    }
                }
            }
            throw new System.Exception("cannot find inner type");
        }

        //@SuppressWarnings("unchecked")
        public Class GetInnerCollectionType(Object sender) //throws Exception 
        {
            if (sender is Type)
            {
                throw new System.Exception("argument of wrong type");
            }
            Class cls = sender.getClass();
            Type objType = cls.GetType();

            if (!(objType == typeof(ParameterizedType)) &&
                cls.getGenericSuperclass() == typeof(ParameterizedType) &&
                typeof(List).getClass().isAssignableFrom(cls.getSuperclass()))
            {
                objType = cls.getGenericSuperclass();
            }

            if (objType == typeof(ParameterizedType))
            {
                return GetInnerType((ParameterizedType)objType);
            }
            else
            {
                if (typeof(List).getClass().isAssignableFrom(sender.getClass()))
                {
                    throw new System.Exception("inner type cannot be found!");
                }
                return typeof(Object).getClass();
            }
        }

        public bool canSetNameForObject(Object obj) //throws Exception 
        {
            XMLDomainObject domainObj = _fs.TryGetDomainObjectByName(GetNameFromClass(obj.getClass()));
            return domainObj != null && domainObj.ProvidesNameMethod() != null && domainObj.ProvidesNameMethod().length() > 0;
        }

        public String getNameFromObject(Object obj) //throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, Exception 
        {
            if (obj is Method)
            {
                Method method = (Method)obj;
                String name = method.getName();
                if (name.startsWith("get"))
                {
                    name = name.substring(3);
                }
                return name;
            }
            else
            {
                XMLDomainObject domainObj = _fs.GetDomainObjectByName(GetNameFromClass(obj.getClass()));
                String name = obj.getClass().getSimpleName() + "_" + obj.GetHashCode();
                if (null != domainObj.ProvidesNameMethod())
                {
                    foreach (Method method in obj.getClass().getMethods())
                    {
                        String methodName1 = method.getName();
                        String methodName2 = methodName1;
                        if (methodName1.startsWith("get"))
                        {
                            methodName2 = methodName1.substring(3);
                        }
                        if (domainObj.ProvidesNameMethod().compareTo(methodName1) == 0 ||
                           domainObj.ProvidesNameMethod().compareTo(methodName2) == 0)
                        {
                            Object nameObj = method.invoke(obj, (Object[])null);
                            if (nameObj != null)
                            {
                                name = nameObj.toString();
                                break;
                            }
                        }
                    }
                }
                return name;
            }
        }

        public bool isDomainObject(Method method) //throws Exception 
        {
            return isFolderObject(method);
        }

        //@SuppressWarnings("unchecked")
        private String GetNameFromClass(Class cls)
        {
            //return cls.getPackage().getName() + "." + cls.getSimpleName();
            return cls.getName();
        }

        public bool isDomainObject(Object obj) //throws Exception 
        {
            return null != _fs.TryGetDomainObjectByName(GetNameFromClass(obj.getClass()));
        }

        //@SuppressWarnings("unchecked")
        public bool isDomainObject(Class cls) //throws Exception 
        {
            return null != _fs.TryGetDomainObjectByName(GetNameFromClass(cls));
        }

        public bool isExecutable(Method method) //throws Exception 
        {
            XMLDomainObject domainObj = _fs.TryGetDomainObjectByName(GetNameFromClass(method.getClass()));
            if (domainObj != null)
            {
                foreach (String executableMethodName in domainObj.ExecutableMethods())
                {
                    if (executableMethodName.compareTo(method.getName()) == 0)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        public bool isFSRoot(Method method) //throws Exception 
        {
            throw new System.Exception("not implemented");
        }

        public bool isFSRoot(Object sender) //throws Exception
        {
            return _fs.RootFolderClassName().compareTo(GetNameFromClass(sender.getClass())) == 0;
        }

        public bool isFolderObject(Method method) //throws Exception 
        {
            XMLDomainObject domainObj = _fs.TryGetDomainObjectByName(GetNameFromClass(method.getClass()));
            if (domainObj != null)
            {
                foreach (String domainMethod in domainObj.FolderMethods())
                {
                    if (domainMethod.compareTo(method.getName()) == 0)
                    {
                        return true;
                    }
                }
            }
            return false || (domainObj != null
                && IsListOfDomainObjects(method.getGenericReturnType().GetType()));
        }

        private bool IsListOfDomainObjects(Type cls) //throws Exception 
        {
            if (cls == typeof(ParameterizedType))
            {
                ParameterizedType pType = (ParameterizedType)cls;
                foreach (Type typeArgument in pType.getActualTypeArguments())
                {
                    if (typeArgument == typeof(Class))
                    {
                        Class typeArgClass = typeArgument.getClass();
                        if (_fs.TryGetDomainObjectByName(GetNameFromClass(typeArgClass)) != null)
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        public bool isFolderObject(Class type, bool strict) //throws Exception 
        {
            return isFolderObject(type);
        }

        public bool isFolderObject(Object obj, bool strict) //throws Exception
        {
            return isFolderObject(obj);
        }

        //@SuppressWarnings("unchecked")
        public bool isFolderObject(Class type) //throws Exception 
        {
            XMLDomainObject domainObj = _fs.TryGetDomainObjectByName(GetNameFromClass(type));
            if (domainObj != null)
            {
                return domainObj.FolderDescription() != null;
            }
            return false;
        }

        public bool isFolderObject(Object obj) //throws Exception 
        {
            XMLDomainObject domainObj = _fs.TryGetDomainObjectByName(GetNameFromClass(obj.getClass()));
            if (domainObj != null)
            {
                return domainObj.FolderDescription() != null;
            }
            return false;
        }

        //@SuppressWarnings("unchecked")
        public void setContainerIfAttributeExists(Object obj, IDomainObjectContainer container) //throws Exception 
        {
            XMLDomainObject domainObj = _fs.GetDomainObjectByName(GetNameFromClass(obj.getClass()));
            if (domainObj.NeedsContainerMethod() != null)
            {
                Method setterMethod = null;
                foreach (Method method in obj.getClass().getMethods())
                {
                    if (method.getReturnType() == typeof(void).getClass() && (
                       method.getName().compareTo(domainObj.NeedsContainerMethod()) == 0 ||
                       (method.getName().startsWith("set") && method.getName().substring(3).compareTo(domainObj.NeedsContainerMethod()) == 0)))
                    {
                        setterMethod = method;
                        break;
                    }
                }
                if (setterMethod == null)
                {
                    throw new System.Exception("could not find container setter named: " + domainObj.NeedsContainerMethod() + " for class " + GetNameFromClass(obj.getClass()));
                }
                setterMethod.invoke(obj, container);
            }
        }

        public void setContainerManagerIfAttributeExists(Object obj, IDomainObjectContainerManager manager) //throws Exception 
        {
            XMLDomainObject domainObj = _fs.GetDomainObjectByName(GetNameFromClass(obj.getClass()));
            if (domainObj.NeedsContainerManagerMethod() != null)
            {
                Method setterMethod = null;
                foreach (Method method in obj.getClass().getMethods())
                {
                    if (method.getReturnType() == typeof(void).getClass() && (
                       method.getName().compareTo(domainObj.NeedsContainerManagerMethod()) == 0 ||
                       (method.getName().startsWith("set") && method.getName().substring(3).compareTo(domainObj.NeedsContainerManagerMethod()) == 0)))
                    {
                        setterMethod = method;
                        break;
                    }
                }
                if (setterMethod == null)
                {
                    throw new System.Exception("could not find container setter named: " + domainObj.NeedsContainerManagerMethod() + " for class " + GetNameFromClass(obj.getClass()));
                }
                setterMethod.invoke(obj, manager);
            }
        }

        public void setNameForObject(Object obj, String name) //throws Exception 
        {
            if (!(obj is Method) && isDomainObject(obj.getClass()))
            {
                XMLDomainObject domainObj = _fs.GetDomainObjectByName(GetNameFromClass(obj.getClass()));
                Method setter = null;
                foreach (Method possibleSetter in obj.getClass().getMethods())
                {
                    if (possibleSetter.getReturnType() == typeof(void).getClass() &&
                       possibleSetter.getParameterTypes().Length == 1 &&
                       possibleSetter.getParameterTypes()[0] == typeof(String).getClass() &&
                       (possibleSetter.getName().compareTo(domainObj.ProvidesNameMethod()) == 0 ||
                        possibleSetter.getName().startsWith("set") && possibleSetter.getName().substring(3).compareTo(domainObj.ProvidesNameMethod()) == 0))
                    {
                        setter = possibleSetter;
                        break;
                    }
                }
                if (setter != null)
                {
                    setter.invoke(obj, new Object[] { name });
                }
            }
        }

        public IGetterSetterPair GetLastAccessTimePair(Object obj) //throws Exception 
        {
            XMLDomainObject domainObj = _fs.GetDomainObjectByName(GetNameFromClass(obj.getClass()));
            return new XMLGetterSetterPair(
                    obj, typeof(Date).getClass(),
                    domainObj.ProvidesLastAccessTimeMethod());
        }

        public IGetterSetterPair GetLastModifiedTimePair(Object obj) //throws Exception 
        {
            XMLDomainObject domainObj = _fs.GetDomainObjectByName(GetNameFromClass(obj.getClass()));
            return new XMLGetterSetterPair(
                    obj, typeof(Date).getClass(),
                    domainObj.ProvidesLastModifiedTimeMethod());
        }

        public IGetterSetterPair GetCreateTimePair(Object obj) //throws Exception 
        {
            XMLDomainObject domainObj = _fs.GetDomainObjectByName(GetNameFromClass(obj.getClass()));
            return new XMLGetterSetterPair(
                    obj, typeof(Date).getClass(),
                    domainObj.ProvidesCreateTimeMethod());
        }

        public IGetterSetterPair GetModePair(Object obj) //throws Exception 
        {
            XMLDomainObject domainObj = _fs.GetDomainObjectByName(GetNameFromClass(obj.getClass()));
            return new XMLGetterSetterPair(
                    obj, typeof(Date).getClass(),
                    domainObj.ProvidesModeMethod());
        }

        public IGetterSetterPair GetUIDPair(Object obj) //throws Exception 
        {
            XMLDomainObject domainObj = _fs.GetDomainObjectByName(GetNameFromClass(obj.getClass()));
            return new XMLGetterSetterPair(
                    obj, typeof(Date).getClass(),
                    domainObj.ProvidesUIDMethod());
        }

        public IGetterSetterPair GetGIDPair(Object obj) //throws Exception 
        {
            XMLDomainObject domainObj = _fs.GetDomainObjectByName(GetNameFromClass(obj.getClass()));
            return new XMLGetterSetterPair(
                    obj, typeof(Date).getClass(),
                    domainObj.ProvidesGIDMethod());
        }

        public bool HasAnyCustomMetadataManagement(Object obj) //throws Exception
        {
            XMLDomainObject domainObj = _fs.GetDomainObjectByName(GetNameFromClass(obj.getClass()));
            return
                domainObj.ProvidesCreateTimeMethod() != null ||
                domainObj.ProvidesLastAccessTimeMethod() != null ||
                domainObj.ProvidesLastModifiedTimeMethod() != null ||
                domainObj.ProvidesUIDMethod() != null ||
                domainObj.ProvidesGIDMethod() != null ||
                domainObj.ProvidesModeMethod() != null;
        }
    }

}
